using System.Diagnostics;
using System.Runtime.CompilerServices;
using Pinwheel.Toolkits;

namespace Pinwheel.LSerialize
{
    public static unsafe partial class LSerializer
    {
        [Conditional("DEBUG")]
        public static void DumpBuffer(byte* buffer, int index, int length, string prefix = "")
        {
            prefix = string.IsNullOrEmpty(prefix) ? "Default" : prefix;
            byte[] bytes = new byte[length];
            fixed (byte* ptr = bytes)
            {
                Native.Utils.Copy(ptr, buffer, length);
            }
            Log.Info($"{prefix} index {index}-{index + length}, size {length}, data: {BitConverter.ToString(bytes)}");
        }
        
        public static int PackSeg(byte* src, byte* dst, int n)
        {
            byte header = 0;
            int notzero = 0;

            byte* header_ptr = dst;
            ++dst;

            for (int i = 0; i < 8; i++)
            {
                if (src[i] != 0)
                {
                    notzero++;
                    header |= (byte)(1 << i);
                    *dst = src[i];
                    ++dst;
                }
            }
            
            if ((notzero == 7 || notzero == 6) && n > 0) {
                notzero = 8;
            }
            
            if (notzero == 8) {
                if (n > 0) {
                    return 8;
                } else {
                    return 10;
                }
            }
            
            if (null != header_ptr) 
                *header_ptr = header;
            
            return notzero + 1;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static void write_ff(byte* src, byte* dst, int n)
        {
            dst[0] = 0xff;
            dst[1] = (byte)(n - 1);
            Native.Utils.Copy(dst + 2, src, n * 8);
        }

        public static int Pack(LStreamBuffer* src, LStreamBuffer* dst)
        {
            src->Seek(0, SeekOrigin.Begin);
            dst->Seek(0, SeekOrigin.Begin);
            
            int ff_srcstart = 0;
            int ff_desstart = 0;
            int ff_n = 0;
            int size = 0;
            
            if (src->Size < 8) src->EnsureSize(8);
            for (int i = 0; i < src->Size; i += 8)
            {
                dst->EnsureSize(10);
                int n = PackSeg(src->Current, dst->Current, ff_n);
                if (n == 10)
                {
                    // first FF
                    ff_srcstart = src->Index;
                    ff_desstart = dst->Index;
                    ff_n = 1;
                }
                else if (n == 8 && ff_n > 0)
                {
                    ++ff_n;
                    if (ff_n == 256)
                    {
                        write_ff(src->Head + ff_srcstart, dst->Head + ff_desstart, ff_n);
                        // DumpBuffer(src->Head + ff_srcstart, ff_srcstart, ff_n * 8, "Pack src 0xff");
                        // DumpBuffer(dst->Head + ff_desstart, ff_desstart, ff_n * 8 + 2, "Pack dest 0xff");
                        dst->Flush();
                        ff_n = 0;
                    }
                }
                else
                {
                    if (ff_n > 0)
                    {
                        write_ff(src->Head + ff_srcstart, dst->Head + ff_desstart, ff_n);
                        // DumpBuffer(src->Head + ff_srcstart, ff_srcstart, ff_n * 8, "Pack src 0xff");
                        // DumpBuffer(dst->Head + ff_desstart, ff_desstart, ff_n * 8 + 2, "Pack dest 0xff");
                        dst->Flush();
                        ff_n = 0; 
                    }
                }
                src->Seek(8, SeekOrigin.Current);
                dst->Seek(n, SeekOrigin.Current);
                dst->Flush();
                size += n;
            }

            if (ff_n > 0)
            {
                write_ff(src->Head + ff_srcstart, dst->Head + ff_desstart, ff_n);
                // DumpBuffer(src->Head + ff_srcstart, ff_srcstart, ff_n * 8, "Pack src 0xff");
                // DumpBuffer(dst->Head + ff_desstart, ff_desstart, ff_n * 8 + 2, "Pack dest 0xff");
            }
            
            dst->Flush();
            return size;
        }

        public static int Unpack(LStreamBuffer* src, LStreamBuffer* dst)
        {
            src->Seek(0, SeekOrigin.Begin);
            dst->Seek(0, SeekOrigin.Begin);
            
            int size = 0;
            while (src->Remain > 0)
            {
                byte header = src->PopByte();
                if (header == 0xff)
                {
                    int n = (src->PopByte() + 1) * 8;
                    src->EnsureTail(n);
                    dst->EnsureSize(n);
                    Native.Utils.Copy(dst->Current, src->Current, n);

                    // DumpBuffer(src->Current, src->Index, n, "Unpack src 0xff");
                    // DumpBuffer(dst->Current, dst->Index, n, "Unpack dest 0xff");
                    
                    src->Seek(n, SeekOrigin.Current);
                    dst->Seek(n, SeekOrigin.Current);
                    size += n;
                }
                else
                {
                    var index = dst->Index;
                    byte* dest = dst->Current;
                    for (int i = 0; i < 8; i++)
                    {
                        int nz = (header >> i) & 1;
                        var data = nz == 1 ? src->PopByte() : (byte)0;
                        dst->PushByte(data);
                        ++size;
                    }
                    // DumpBuffer(dest, index, 8, "Unpack");
                }
            }

            dst->Reset(size);
            return size; 
        }
    }
}